package panda.cardutil.other;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import panda.client.engine.MagicCard;
import panda.client.engine.MagicCardDB;
import panda.client.enumerations.CardRarity;
import panda.client.enumerations.MagicSet;

/**
 * Creates properly distributed booster and tournament packs.
 *
 * @author zane.tian@gmail.com
 */
public class LimitedFactory
{
	private static LimitedFactory instance;
	private static final MagicSet baseSet = MagicSet.M_10; // sets where the lands come from

	private LimitedFactory()
	{

	}

	public static LimitedFactory get()
	{
		if (instance == null)
		{
			instance = new LimitedFactory();
		}

		return instance;
	}

	/**
	 * Booster packs from Shards of Alara (and beyond) will contain:<br>
	 * 1 Rare<br>
	 * 3 Uncommon<br>
	 * 10 Common<br>
	 * 1 Basic Land<br>
	 * <br>
	 * Roughly 1 out of 8 packs will contain a Mythic Rare in place of the Rare :D <br>
	 * <br>
	 * TODO: check for post-shards.
	 *
	 * @param set
	 *
	 * @return
	 */
	public List<MagicCard> getPostShardsBoosterPack(MagicSet set)
	{
		List<MagicCard> boosterPack = new ArrayList<MagicCard>();

		/** Does not care about foils or basic lands :P */
		double chance = Math.random();
		// System.out.println("Mythic Rare Chance: " + chance);

		if (chance < 0.125)
		{
			boosterPack.addAll(getRandomNonDuplicateCards(1, CardRarity.MYTHIC, set));
		}
		else
		{
			boosterPack.addAll(getRandomNonDuplicateCards(1, CardRarity.RARE, set));
		}

		boosterPack.addAll(getRandomNonDuplicateCards(3, CardRarity.UNCOMMON, set));

		/* one out of every 3 boosters, we have a foil! this is a rough approximation */
		int foilChance = getRandInt(2);
		if (foilChance != 0)
		{
			boosterPack.addAll(getRandomNonDuplicateCards(10, CardRarity.COMMON, set));

			/* hack: should be set to whatever the current base set is */
			boosterPack.addAll(getRandomNonDuplicateCards(1, CardRarity.LAND, baseSet));
		}
		else
		{
			boosterPack.addAll(getRandomNonDuplicateCards(9, CardRarity.COMMON, set));

			/* we can always tell if there is a foil if there's a card following the basic land */
			boosterPack.addAll(getRandomNonDuplicateCards(1, CardRarity.LAND, baseSet));

			/**
			 * We have a foil! Supposedly foils are calculated such that foils are proportionally as rare as their
			 * non foil counterparts. In a single pack, there is 1 rare, 3 uncommons, 10 commons, and 1 basic land.
			 * In addition, there is a 1/8 chance that the rare is replaced by a mythic rare. The chances are:
			 * 1 mythic : 8 rare : 24 uncommon : 80 common : 8 basic land, totaling 121 possibilities for our single
			 * foil.
			 */
			final int mChance = 1;
			final int rChance = 8;
			final int uChance = 24;
			final int cChance = 80;
			final int bChance = 8;

			/* an integer between 1 and the sum of all the possibilities. */
			int foilRoll = 1 + getRandInt(mChance + rChance + uChance + cChance + bChance - 1);

			if (foilRoll <= mChance)
			{
				boosterPack.addAll(getRandomNonDuplicateCards(1, CardRarity.MYTHIC, set));
			}
			else if (foilRoll <= mChance + rChance)
			{
				boosterPack.addAll(getRandomNonDuplicateCards(1, CardRarity.RARE, set));
			}
			else if (foilRoll <= mChance + rChance + uChance)
			{
				boosterPack.addAll(getRandomNonDuplicateCards(1, CardRarity.UNCOMMON, set));
			}
			else if (foilRoll <= mChance + rChance + uChance + cChance)
			{
				boosterPack.addAll(getRandomNonDuplicateCards(1, CardRarity.COMMON, set));
			}
			else if (foilRoll <= mChance + rChance + uChance + cChance + bChance)
			{
				boosterPack.addAll(getRandomNonDuplicateCards(1, CardRarity.LAND, baseSet));
			} else {
				/* these are all mutually exclusive  */
				throw new IllegalStateException("invalid roll for foil: " + foilRoll);
			}
		}


		return boosterPack;
	}

	/**
	 * Returns the contents of one booster pack. The distribution ratios are as follows:<br>
	 * 1 rare<br>
	 * 3 uncommon<br>
	 * 11 common (before shards) or 10 commons + 1 basic land (shards and beyond)<br>
	 * Foil cards replace a normal card at the rate of 1 foil per ~70 cards. A foil always replaces a common slot and can
	 * be any of the rarities including basic land. Common foils appear in 1/12 boosters, Uncommon foils 1/18, and Rare
	 * foils 1/36.<br>
	 * TODO: no idea of land foil distribution. Assuming foil lands appear in a common foil slot.<br>
	 * After shards of alara, boosters will also contain the new 'mystic rare' type, which may or may not appear in a
	 * booster.
	 */
	@Deprecated
	public List<MagicCard> getPreShardsBoosterPack(MagicSet set)
	{
		List<MagicCard> boosterPack = new ArrayList<MagicCard>();

		/** Does not care about foils or basic lands :P */
		boosterPack.addAll(getRandomNonDuplicateCards(1, CardRarity.RARE, set));
		boosterPack.addAll(getRandomNonDuplicateCards(3, CardRarity.UNCOMMON, set));
		boosterPack.addAll(getRandomNonDuplicateCards(11, CardRarity.COMMON, set));

		// for(MagicCard card : boosterPack) {
		// System.out.println(card.getRarity().getRarity() + ": " + card.getName());
		// }

		return boosterPack;
	}

	/**
	 * Returns the contents of one tournament pack. The distribution ratios are as follows:<br>
	 * 3 rare: each rare will have a 1/8 chance to be a mythic rare instead.<br>
	 * 10 uncommon<br>
	 * 32 common<br>
	 * TODO: make sure the set is post shards + figure out foil percentages.
	 */
	@Deprecated
	public List<MagicCard> getPostShardsTournamentPack(MagicSet set)
	{
		List<MagicCard> tourneyPack = new ArrayList<MagicCard>();
		double mythicChance = 0.125;

		/**
		 * Determine the probability of getting mythic rares:<br>
		 * p(get 0 MR): (7/8)^3 = 343/512. Multiply chances of not seeing a MR in each rare slot.<br>
		 * p(get 1 MR): 3 * (1/8) * (7/8)^2 = 147/512. You can get the MR in any of the 3 slots.<br>
		 * p(get 2 MR): 3 * (1/8)^2 * (7/8) = 21/512.<br>
		 * p(get 3 MR): (1/8)^3 = 1/512.<br>
		 */
		double firstMythic = Math.random();
		double secondMythic = Math.random();
		double thirdMythic = Math.random();

		int mythicsReceived = 0;
		// System.out.println("Mythic Rare Chance: " + chance);

		if (firstMythic < mythicChance)
		{
			mythicsReceived++;
		}

		if (secondMythic < mythicChance)
		{
			mythicsReceived++;
		}

		if (thirdMythic < mythicChance)
		{
			mythicsReceived++;
		}

		switch (mythicsReceived)
		{
			case 3:
				tourneyPack.addAll(getRandomNonDuplicateCards(3, CardRarity.MYTHIC, set));
				break;
			case 2:
				tourneyPack.addAll(getRandomNonDuplicateCards(2, CardRarity.MYTHIC, set));
				tourneyPack.addAll(getRandomNonDuplicateCards(1, CardRarity.RARE, set));
				break;
			case 1:
				tourneyPack.addAll(getRandomNonDuplicateCards(1, CardRarity.MYTHIC, set));
				tourneyPack.addAll(getRandomNonDuplicateCards(2, CardRarity.RARE, set));
				break;
			case 0:
				tourneyPack.addAll(getRandomNonDuplicateCards(3, CardRarity.RARE, set));
				break;
			default:
				throw new IllegalArgumentException("Invalid number of mythic rares received: " + mythicsReceived);
		}

		tourneyPack.addAll(getRandomNonDuplicateCards(10, CardRarity.UNCOMMON, set));
		tourneyPack.addAll(getRandomNonDuplicateCards(32, CardRarity.COMMON, set));

		return tourneyPack;
	}

	/**
	 * Returns the contents of one tournament pack. The distribution ratios are as follows:<br>
	 * 3 rare<br>
	 * 10 uncommon<br>
	 * 32 common<br>
	 * TODO: figure out foil percentages.
	 */
	@Deprecated
	public List<MagicCard> getTournamentPack(MagicSet set)
	{
		List<MagicCard> tourneyPack = new ArrayList<MagicCard>();

		/**
		 * A foil will appear in one out of 90 cards:<br>
		 */
		tourneyPack.addAll(getRandomNonDuplicateCards(3, CardRarity.RARE, set));
		tourneyPack.addAll(getRandomNonDuplicateCards(10, CardRarity.UNCOMMON, set));
		tourneyPack.addAll(getRandomNonDuplicateCards(32, CardRarity.COMMON, set));

		// for(MagicCard card : tourneyPack) {
		// System.out.println(card.getRarity().getRarity() + ": " + card.getName());
		// }

		return tourneyPack;
	}

	/**
	 * Returns a random card of the given rarity from a given MagicSet. Implementation details: keeps randomly picking
	 * cards from the set until one of the correct rarity is selected. Creates a cardDB of the entire set every time this
	 * method is called - this is very inefficient.<br>
	 * <br>
	 * Given n = number of cards desired and s = total cards in the set, this method -should- generate cards -not- picked
	 * if n > s/2 and then populate the returned List of cards with the complement of what was picked.
	 *
	 * @param rarity
	 * @param set
	 *
	 * @return
	 */
	@Deprecated
	public MagicCard getRandomCard(CardRarity rarity, MagicSet set)
	{
		int tries = 0;
		boolean found = false;
		MagicCard match = null;

		List<MagicCard> cardDB = MagicCardDB.getCardsFromSet(set);

		while (!found)
		{
			match = cardDB.get(getRandInt(cardDB.size() - 1));
			if (match.getRarity().equals(rarity))
			{
				found = true;
			}
			tries++;
		}

		assert match != null : "a match was not found for rarity " + rarity + " in set " + set;
		System.out.print(" " + tries + " ");
		return match;
	}

	/**
	 * Returns a list of random non-duplicate cards of a given rarity from a given MagicSet.<br>
	 * TODO Implementation details:<br>
	 * Iterate through all the cards in the set, adding cards of the specified rarity to a list. Then shuffle this list
	 * and take the sublist from 0 to n-1. These are guaranteed to be non duplicate cards of the specified rarity.<br>
	 * <br>
	 *
	 * @param n:     take care to make sure n does not exceed the number of cards of the specified rarity in the set
	 * @param rarity
	 * @param set
	 *
	 * @return
	 */
	private List<MagicCard> getRandomNonDuplicateCards(int n, CardRarity rarity, MagicSet set)
	{
		/* obtain a list(set) of cards of the specified rarity of cards in the specified set */
		List<MagicCard> sameRarityCards = new ArrayList<MagicCard>();
		List<MagicCard> setDB = MagicCardDB.getCardsFromSet(set);

		for (MagicCard card : setDB)
		{
			assert !sameRarityCards.contains(card) : "duplicate card found in DB: " + card.getName();
			if (card.getRarity().equals(rarity))
			{
				sameRarityCards.add(card);
			}
		}

		/* make sure the list of same rarity cards is larger than n */
		if (n > sameRarityCards.size())
		{
			throw new IllegalArgumentException("n (" + n + ") is larger than the size of " + rarity + " cards in " + set
					+ ": " + sameRarityCards.size());
		}

		Collections.shuffle(sameRarityCards);
		return sameRarityCards.subList(0, n);
	}

	/**
	 * Returns a random integer in the range [0, max]
	 */
	private int getRandInt(int max)
	{
		return (int) Math.round(Math.random() * max);
	}
}